/*
 * Copyright (c) 2009-2012 Panxiaobo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.banquanhu.zip;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

/**
 * create a signed jar in quick way
 * 
 * @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
 * 
 */
public class TinySign {

    private static void doDir(String prefix, File dir, ZipOutputStream zos, DigestOutputStream dos, Manifest m)
            throws IOException {
        zos.putNextEntry(new ZipEntry(prefix));
        zos.closeEntry();
        for (File f : dir.listFiles()) {
            if (f.isFile()) {
                doFile(prefix + f.getName(), f, zos, dos, m);
            } else {
                doDir(prefix + f.getName() + "/", f, zos, dos, m);
            }
        }
    }

    private static void doFile(String name, File f, ZipOutputStream zos, DigestOutputStream dos, Manifest m)
            throws IOException {
        ZipEntry zipEntry = new ZipEntry(name);
        // 目前碰到很多mp3压缩后无法运行的app
        if (f.getAbsolutePath().endsWith("mp3") || f.getAbsolutePath().endsWith("mid")
                || f.getAbsolutePath().endsWith("ogg") || f.getAbsolutePath().endsWith("mp4")
                || f.getAbsolutePath().endsWith("m4a") || f.getAbsolutePath().endsWith("wav")
                || f.getAbsolutePath().endsWith("gif") || f.getAbsolutePath().endsWith("svga")) {
            /** ZipEntry.STORED */
            zipEntry.setMethod(ZipEntry.STORED);
            zipEntry.setCompressedSize(f.length());
            zipEntry.setSize(f.length());
            CRC32 crc = new CRC32();
            crc.update(getFileBytes(f));
            zipEntry.setCrc(crc.getValue());
        } else {
            zipEntry.setMethod(ZipEntry.DEFLATED);
        }

        zos.putNextEntry(zipEntry);
        FileInputStream fis = FileUtils.openInputStream(f);
        IOUtils.copy(fis, dos);
        IOUtils.closeQuietly(fis);
        byte[] digets = dos.getMessageDigest().digest();
        zos.closeEntry();
        Attributes attr = new Attributes();
        attr.putValue("SHA1-Digest", eBase64(digets));
        m.getEntries().put(name, attr);
    }

    public static byte[] getFileBytes(File file) throws FileNotFoundException, IOException {
        byte[] buffer;
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        bos.close();
        buffer = bos.toByteArray();
        return buffer;
    }

    private static String eBase64(byte[] data) {
        return new String(Base64.encodeBase64(data));
    }

    /**
     * create a signed jar from dir to out
     * 
     * @param dir
     *            a folder contains file
     * @param file
     * @param out
     *            the output jar
     * @throws Exception
     */
    public static void sign(File dir, File file, OutputStream out) throws Exception {
        ZipOutputStream zos = new ZipOutputStream(out);
        Manifest manifest = new Manifest();
        String sha1Manifest = writeMF(dir, file, manifest, zos);
        IOUtils.closeQuietly(zos);
    }

    private static String writeMF(File dir, File file, Manifest manifest, ZipOutputStream zos)
            throws NoSuchAlgorithmException, IOException {
        MessageDigest md = MessageDigest.getInstance("SHA1");
        DigestOutputStream dos = new DigestOutputStream(zos, md);
        zipAndSha1(dir, file, zos, dos, manifest);
        Attributes main = manifest.getMainAttributes();
        main.putValue("Manifest-Version", "1.0");
        main.putValue("Created-By", "tiny-sign-" + TinySign.class.getPackage().getImplementationVersion());
        // zos.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF"));
        // manifest.write(dos);
        // zos.closeEntry();
        return eBase64(md.digest());
    }

    private static void zipAndSha1(File dir, File file, ZipOutputStream zos, DigestOutputStream dos, Manifest m)
            throws NoSuchAlgorithmException, IOException {
        for (File f : dir.listFiles()) {
            if (!f.getName().startsWith("META-INF")) {
                if (f.isFile()) {
                    if (!file.equals(f)) {
                        doFile(f.getName(), f, zos, dos, m);
                    }
                } else {
                    doDir(f.getName() + "/", f, zos, dos, m);
                }
            }
        }
    }
}
